package com.gmrz.webauthn.common;

import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.*;
import com.gmrz.webauthn.protocol.v1.processor.WebAuthnCborException;
import com.gmrz.webauthn.protocol.v1.processor.WebAuthnErrorCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


public class CBORDecodeUtil {
    private static final Logger LOG = LogManager.getLogger(CBORDecodeUtil.class);


    public static List<DataItem> cborDecode(byte[] cborData)
            throws WebAuthnCborException {
        ByteArrayInputStream bais = new ByteArrayInputStream(cborData);
        List<DataItem> dataitems;
        try {
            dataitems = new CborDecoder(bais).decode();
        } catch (CborException e) {
            String errorMsg = "CBOR decode failed : ";
            LOG.error(errorMsg, e);
            throw new WebAuthnCborException(WebAuthnErrorCode.ERROR_CBOR_DECODING_FAILED, e);
        }
        return dataitems;
    }

    public static java.util.Map<String, Object> cborMapConversion(co.nstant.in.cbor.model.Map cborMap) throws WebAuthnCborException {
        LOG.debug(" decoding cbor map");
        java.util.Map<String, Object> decodedMap = new HashMap<String, Object>();

        for (DataItem dataItem : cborMap.getKeys()) {
            DataItem dataItemValue = cborMap.get(dataItem);
            Object value = getDecodedValue(dataItemValue);

            decodedMap.put(dataItem.toString(), value);
        }

        return decodedMap;
    }

    private static Object getDecodedValue(DataItem value) throws WebAuthnCborException {
        LOG.debug(" decoding the following dataItem : {}", value.toString());
        if (value.getMajorType() == MajorType.BYTE_STRING) {
            ByteString valueByteStr = (ByteString) value;
            return valueByteStr.getBytes();
        }
        if (value.getMajorType() == MajorType.UNICODE_STRING) {
            UnicodeString valueUnicodeStr = (UnicodeString) value;
            return valueUnicodeStr.getString();
        }
        if (value.getMajorType() == MajorType.UNSIGNED_INTEGER) {
            UnsignedInteger valueUnsignedInt = (UnsignedInteger) value;
            return valueUnsignedInt.getValue();
        }
        if (value.getMajorType() == MajorType.NEGATIVE_INTEGER) {
            NegativeInteger valueNegativeInt = (NegativeInteger) value;
            return valueNegativeInt.getValue();
        }
        if (value.getMajorType() == MajorType.MAP) {

            return cborMapConversion((co.nstant.in.cbor.model.Map) value);
        }
        if (value.getMajorType() == MajorType.ARRAY) {
            Array valueByteArray = (Array) value;
            List<DataItem> arrayList = valueByteArray.getDataItems();

            List<Object> arrayListDecoded = new ArrayList<Object>();

            if (arrayList != null) {
                for (DataItem item : arrayList) {
                    arrayListDecoded.add(getDecodedValue(item));
                }
            }

            return arrayListDecoded;
        }
        if ((value.getMajorType() == MajorType.SPECIAL) && ((value instanceof DoublePrecisionFloat))) {
            DoublePrecisionFloat valueDoublePrecisionFloat = (DoublePrecisionFloat) value;
            return valueDoublePrecisionFloat.getValue();
        }

        String errorMsg = "unsupported major type : {}, dataitem name : {} ";
        LOG.error(errorMsg, value.getMajorType(), value.toString());
        throw new WebAuthnCborException(WebAuthnErrorCode.ERROR_CBOR_DECODING_FAILED, errorMsg);
    }
}
